package com.dongrj.framework.core.utils;

import com.dongrj.framework.core.exception.Assert;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Range;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.*;

/**
 * @author dongrongjun
 * @date 2020/08/04
 * 精确数字计算
 */
public final class BigDecimalUtils {

    /**
     * 转字符串
     *
     * @param bigDecimal 数字
     * @param digit      小数点位数
     */
    public static String toString(BigDecimal bigDecimal, Integer digit) {
        bigDecimal = nullToZero(bigDecimal);
        String format = Strings.padEnd("0.", digit + 2, '0');
        DecimalFormat decimalFormat = new DecimalFormat(format);
        return decimalFormat.format(bigDecimal);
    }

    /**
     * 转数字
     *
     * @param bigDecimal 数字
     * @param digit      小数点位数
     */
    public static Double toDouble(BigDecimal bigDecimal, Integer digit) {
        return Double.parseDouble(toString(bigDecimal, digit));
    }


    /**
     * 数字转字符串
     *
     * @param number 数字
     * @param digit  小数点位数
     */
    public static String toString(double number, Integer digit) {
        Preconditions.checkArgument(!Double.isNaN(number), "数字不合法!");
        String str = "0.";
        String format = Strings.padEnd(str, digit + str.length(), '0');
        DecimalFormat decimalFormat = new DecimalFormat(format);
        BigDecimal bigDecimal = new BigDecimal(number);
        return decimalFormat.format(bigDecimal);
    }

    /**
     * 比较大小
     *
     * @param bigDecimal1 数字1
     * @param bigDecimal2 数字2
     */
    public static int compareTo(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return nullToZero(bigDecimal1).compareTo(nullToZero(bigDecimal2));
    }

    /**
     * 判断是否都相等
     *
     * @param bigDecimals 数字集合
     */
    public static boolean isEqual(BigDecimal... bigDecimals) {
        if (Objects.isNull(bigDecimals) || bigDecimals.length == 1) {
            return true;
        }
        BigDecimal first = bigDecimals[0];
        for (BigDecimal bigDecimal : bigDecimals) {
            if (bigDecimal.compareTo(nullToZero(first)) != 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断是否都不相等
     *
     * @param bigDecimals 数字集合
     */
    public static boolean isNotEqual(BigDecimal... bigDecimals) {
        return !isEqual(bigDecimals);
    }

    /**
     * 判断区间[]
     *
     * @param bigDecimal1 数字1
     * @param bigDecimal2 数字2
     * @param bigDecimal3 数字3
     */
    public static boolean isBetweenClose(BigDecimal bigDecimal1, BigDecimal bigDecimal2, BigDecimal bigDecimal3) {
        return Range.closed(nullToZero(bigDecimal1), nullToZero(bigDecimal3)).contains(nullToZero(bigDecimal2));
    }


    /**
     * 判断区间()
     *
     * @param bigDecimal1 数字1
     * @param bigDecimal2 数字2
     * @param bigDecimal3 数字3
     */
    public static boolean isBetweenOpen(BigDecimal bigDecimal1, BigDecimal bigDecimal2, BigDecimal bigDecimal3) {
        return Range.open(nullToZero(bigDecimal1), nullToZero(bigDecimal3)).contains(nullToZero(bigDecimal2));
    }

    /**
     * 判断区间[)
     *
     * @param bigDecimal1 数字1
     * @param bigDecimal2 数字2
     * @param bigDecimal3 数字3
     */
    public static boolean isBetweenCloseOpen(BigDecimal bigDecimal1, BigDecimal bigDecimal2, BigDecimal bigDecimal3) {
        return Range.closedOpen(nullToZero(bigDecimal1), nullToZero(bigDecimal3)).contains(nullToZero(bigDecimal2));
    }

    /**
     * 判断区间(]
     *
     * @param bigDecimal1 数字1
     * @param bigDecimal2 数字2
     * @param bigDecimal3 数字3
     */
    public static boolean isBetweenOpenClose(BigDecimal bigDecimal1, BigDecimal bigDecimal2, BigDecimal bigDecimal3) {
        return Range.openClosed(nullToZero(bigDecimal1), nullToZero(bigDecimal3)).contains(nullToZero(bigDecimal2));
    }

    /**
     * 判断相等
     *
     * @param bigDecimal1     数字1
     * @param bigDecimal2     数字2
     * @param isNullEqualZero null==Zero
     */
    public static Optional<Integer> compareTo(BigDecimal bigDecimal1, BigDecimal bigDecimal2, boolean isNullEqualZero) {
        if (isNullEqualZero) {
            return Optional.of(compareTo(bigDecimal1, bigDecimal2));
        }
        if (Objects.equals(bigDecimal1, bigDecimal2)) {
            return Optional.of(0);
        }
        if (Objects.isNull(bigDecimal1) || Objects.isNull(bigDecimal2)) {
            return Optional.empty();
        }
        return Optional.of(bigDecimal1.compareTo(bigDecimal2));
    }

    /**
     * 数字除以
     *
     * @param bigDecimal1 数字11
     * @param bigDecimal2 数字2
     */
    public static BigDecimal divideDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2) {
        return divide(bigDecimal1, bigDecimal2, 2, RoundingMode.DOWN);
    }

    /**
     * 数字除以
     *
     * @param bigDecimal1 数字11
     * @param bigDecimal2 数字2
     * @param digit       小数点个数
     */
    public static BigDecimal divideDown(BigDecimal bigDecimal1, BigDecimal bigDecimal2, Integer digit) {
        return divide(bigDecimal1, bigDecimal2, digit, RoundingMode.DOWN);
    }

    public static BigDecimal divide(BigDecimal bigDecimal1, BigDecimal bigDecimal2, Integer digit, RoundingMode roundingMode) {
        Preconditions.checkArgument(digit > 0, "精度位数不能小于1");
        Preconditions.checkArgument(bigDecimal1 != null && bigDecimal2 != null, "运算数值不能为空!");
        Preconditions.checkArgument(bigDecimal2.compareTo(BigDecimal.ZERO) != 0, "除数不能为空!");
        return bigDecimal1.divide(bigDecimal2, digit, roundingMode);
    }

    /**
     * 空转Zero
     *
     * @param bigDecimal 数字
     */
    public static BigDecimal nullToZero(BigDecimal bigDecimal) {
        if (Objects.isNull(bigDecimal)) {
            return BigDecimal.ZERO;
        }
        return bigDecimal;
    }

    /**
     * 空转Null
     *
     * @param bigDecimal 数字
     */
    public static String nullToNull(BigDecimal bigDecimal) {
        if (Objects.isNull(bigDecimal)) {
            return null;
        }
        return bigDecimal.toString();
    }

    /**
     * 字符串空转Zero
     *
     * @param value 数字字符串
     */
    public static BigDecimal emptyToZero(String value) {
        if (StringUtils.isEmpty(value)) {
            return BigDecimal.ZERO;
        }
        try {
            return new BigDecimal(value);
        } catch (Exception e) {
            return BigDecimal.ZERO;
        }
    }

    /**
     * 字符串空转Zero
     *
     * @param value 数字字符串
     */
    public static BigDecimal emptyToNull(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        try {
            return new BigDecimal(value);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 是空或者Zero
     *
     * @param bigDecimal 数字
     */
    public static boolean isNullZero(BigDecimal bigDecimal) {
        return Objects.isNull(bigDecimal) || bigDecimal.compareTo(BigDecimal.ZERO) == 0;
    }

    /**
     * 不是空或者Zero
     *
     * @param bigDecimal 数字
     */
    public static boolean nonNullZero(BigDecimal bigDecimal) {
        return !isNullZero(bigDecimal);
    }


    public static BigDecimal toBigDecimalDown(BigDecimal bigDecimal, Integer digit) {
        bigDecimal = nullToZero(bigDecimal);
        return bigDecimal.setScale(digit, RoundingMode.DOWN);
    }

    public static <T> BigDecimal sumBigDecimal(List<T> list, Function<T, BigDecimal> integerFunction, BigDecimal defaultR) {
        return sum(list, integerFunction, defaultR, BigDecimal::add);
    }

    public static <T> BigDecimal sumBigDecimalDown(int scale, List<T> list, Function<T, BigDecimal> integerFunction, BigDecimal defaultR) {
        BigDecimal sum = sum(list, integerFunction, defaultR, BigDecimal::add);
        return sum.setScale(scale, RoundingMode.DOWN);
    }


    public static <T> BigDecimal sumBigDecimalHalfUp(List<T> list, Function<T, BigDecimal> integerFunction, BigDecimal defaultR) {
        return sumBigDecimalHalfUp(2, list, integerFunction, defaultR);
    }


    public static <T> BigDecimal sumBigDecimalHalfUp(int scale, List<T> list, Function<T, BigDecimal> integerFunction, BigDecimal defaultR) {
        BigDecimal sum = sum(list, integerFunction, defaultR, BigDecimal::add);
        return sum.setScale(scale, RoundingMode.HALF_UP);
    }

    public static <T, R> R sum(List<T> list, Function<T, R> integerFunction, R defaultR, BinaryOperator<R> accumulator) {
        if (CollectionUtils.isEmpty(list)) {
            return defaultR;
        }
        return list.stream().map(integerFunction).filter(Objects::nonNull).reduce(defaultR, accumulator);
    }


    /**
     * @param scale             小数点位数
     * @param shareTotalMoney   分摊总数
     * @param ts                需要分摊的每一项
     * @param resultFieldGetter 结果字段Getter方法
     * @param resultFieldSetter 结果字段Setter方法
     */
    public static <T> void shareBigDecimal(Integer scale, BigDecimal shareTotalMoney, List<T> ts, Function<T, BigDecimal> resultFieldGetter, BiConsumer<T, BigDecimal> resultFieldSetter) {
        if (CollectionUtils.isEmpty(ts) || nullToZero(shareTotalMoney).compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        for (T t : ts) {
            BigDecimal each = resultFieldGetter.apply(t);
            Assert.nonNull(each, "分摊失败!");
            Assert.check(each.compareTo(BigDecimal.ZERO) >= 0, "分摊失败!");
        }
        if (CollectionUtils.sizeEquals(ts, 1)) {
            T t = ts.get(0);
            resultFieldSetter.accept(t, shareTotalMoney);
            return;
        }
        BigDecimal calcTotal = sumBigDecimal(ts, resultFieldGetter, BigDecimal.ZERO);
        for (T t : ts) {
            BigDecimal each = resultFieldGetter.apply(t);
            BigDecimal divide = divideDown(each, calcTotal, scale).multiply(shareTotalMoney);
            resultFieldSetter.accept(t, divide);
        }
        List<T> goodsSubsidyItems = CollectionUtils.subList(ts, 0, ts.size() - 1);
        BigDecimal beforeMoney = sumBigDecimal(goodsSubsidyItems, resultFieldGetter, BigDecimal.ZERO);
        BigDecimal subtract = shareTotalMoney.subtract(beforeMoney);
        T t = ts.get(ts.size() - 1);
        resultFieldSetter.accept(t, subtract);
    }

    public static boolean isZero(BigDecimal bigDecimal) {
        if (Objects.isNull(bigDecimal)) {
            return false;
        }
        return bigDecimal.compareTo(BigDecimal.ZERO) == 0;
    }

    public static <T> void zeroToNull(T t, Supplier<BigDecimal> supplier, Consumer<BigDecimal> consumer) {
        BigDecimal apply = supplier.get();
        if (isZero(apply)) {
            consumer.accept(null);
        }
    }
}
